#! /usr/bin/env python

import os, sys
import pygame
from pygame.locals import *
from helpers import *
from pygame.sprite import *
from copy import copy, deepcopy
from pieces import *

if not pygame.font: print 'Warning, fonts disabled'
##if not pygame.mixer: print 'Warning, sound disabled'

class InterBlock:
    
    def __init__(self, width=800, height=600):
        pygame.init()
        self.width = width
        self.height = height
        pygame.display.set_caption('InterBlock')
        boardSize = min(width, height) / 2
        boardSize = int(boardSize / 20) * 20 # Round to multiple of 20 so pieces fit nicely
        """Create the Screen"""
        self.screen = pygame.display.set_mode((self.width, self.height))
        self.background = pygame.Surface(self.screen.get_size())
        self.background = self.background.convert()
        self.background.fill((0,0,0))
        self.board = Board((width - boardSize) / 2, (height - boardSize) / 2, size=boardSize)
        self.setUpPieces()
        self.pieceBeingDragged = pygame.sprite.RenderPlain()
        self.pieceBeingRotated = pygame.sprite.RenderPlain()
        self.lastClickTime = 0
    
    def mainLoop(self):
        while True:
            # The wait() is so we block when nothing's happening, but we also want the get() so we clear out the event
            # queue before drawing. Drawing once per event is too slow (ie the drawing starts lagging behind the events)
            event = pygame.event.wait()
            self.handleEvent(event)
            for event in pygame.event.get():
                self.handleEvent(event)
            
            # Do the Drawing
            self.screen.blit(self.background, (0, 0))
            if pygame.font:
                font = pygame.font.Font(None, 36)
                text = font.render("InterBlock", 1, (255, 255, 0))
                textpos = text.get_rect(centerx=self.background.get_width()/2)
                #self.screen.blit(text, textpos)
            self.board.draw(self.screen)
            self.pieces.draw(self.screen)
            pygame.display.flip()
    
    def handleEvent(self, event):
        if event.type == pygame.QUIT: 
            sys.exit()
        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            currentClickPiece = None
            for piece in self.pieces:
                if piece.rect.collidepoint(event.pos[0], event.pos[1]):
                    currentClickPiece = piece
                    if pygame.time.get_ticks() - self.lastClickTime < 400 and piece == self.lastClickPiece:
                        piece.flipVertically()
                        currentClickPiece = None
                    if event.button == 1: # TODO constant?
                        if piece.startMoving():
                            self.pieceBeingDragged.add(piece)
                            break
                    elif event.button == 3:
                        if piece.startRotating():
                            self.pieceBeingRotated.add(piece)
                            break
            self.lastClickTime = pygame.time.get_ticks()
            self.lastClickPiece = currentClickPiece
        elif event.type == pygame.MOUSEBUTTONUP:
            if event.button == 1: # TODO constant?
                if self.pieceBeingDragged:
                    self.pieceBeingDragged.sprites()[0].stopMoving()
                    self.pieceBeingDragged.empty()
            elif event.button == 3:
                if self.pieceBeingRotated:
                    self.pieceBeingRotated.sprites()[0].stopRotating()
                    self.pieceBeingRotated.empty()
        elif event.type == pygame.MOUSEMOTION:
            if self.pieceBeingDragged:
                self.pieceBeingDragged.sprites()[0].moveBy(event.rel)
            if self.pieceBeingRotated:
                self.pieceBeingRotated.sprites()[0].rotateBy(-event.rel[0])
            # Check if mouse is over piece so piece manipulators should be shown
            # TODO assert len(pieceUnderMouse) <= 1
    
    def setUpPieces(self):
        self.pieces = pygame.sprite.RenderPlain()
        startingPositions = [
            [Single, 0, 0],
            [Tee, 2, 0],
            [Line, 6, 0],
            [LittleStep, 0, 3],
            [Double, 4, 3],
            [TripleLine, 6, 2],
            [QuadrupleLine, 0, 6],
            [Box, 12, 0],
            [Plus, 8, 2],
            [U, 5, 6],
            [Steps, 0, 8],
            [ZigZag, 4, 9],
            [BigTee, 0, 12],
            [BigL, 8, 9],
            [LittleL, 9, 6],
            [BigPipe, 15, 0],
            [LittlePipe, 12, 3],
            [Blob, 4, 13],
            [Lef, 16, 3],
            [SignPost, 20, 0],
            [Ledge, 0, 16]
        ]
        pieceSize = self.board.rect().width / 20
        # Convert starting positions to screen coordinates
        for piece in startingPositions:
            piece[1] = piece[1] * pieceSize + 10
            piece[2] = piece[2] * pieceSize + 10
        def oneMoveBy(piece, move):
            piece.startMoving()
            piece.moveBy(move)
            piece.stopMoving()
        for piece in startingPositions:
            self.pieces.add(piece[0](self.board, 'red', piece[1], piece[2], size=pieceSize))
            newPiece = piece[0](self.board, 'yellow', self.width - piece[1], piece[2], size=pieceSize)
            oneMoveBy(newPiece, (-newPiece.width(), 0))
            self.pieces.add(newPiece)
            newPiece = piece[0](self.board, 'green', self.width - piece[1], self.height - piece[2], size=pieceSize)
            oneMoveBy(newPiece, (-newPiece.width(), -newPiece.height()))
            self.pieces.add(newPiece)
            newPiece = piece[0](self.board, 'blue', piece[1], self.height - piece[2], size=pieceSize)
            oneMoveBy(newPiece, (0, -newPiece.height()))
            self.pieces.add(newPiece)
        """ Idea for generic piece placement algorithm:
        Make sweeps along the relevant edges of "squares" with increasing side length.
        Keep moving the piece to the new spot and checking to see if it collides. """

class Board:
    def __init__(self, x = 0, y = 0, size = 500):
        self.boardSprite = Sprite()
        size = size + 1
        self.boardSprite.image = Surface((size, size))
        self.boardSprite.rect = self.boardSprite.image.get_rect()
        self.boardSprite.rect.move_ip(x, y)
        self.boardGroup = Group(self.boardSprite)
        white = (255, 255, 255)
        for i in range(21):
            pygame.draw.line(self.boardSprite.image, white, (0, i * size / 20), (size - 1, i * size / 20))
            pygame.draw.line(self.boardSprite.image, white, (i * size / 20, 0), (i * size / 20, size - 1))
        # TODO This is kind of a hack- these should be drawable in the loop:
        pygame.draw.line(self.boardSprite.image, white, (0, size - 1), (size - 1, size - 1))
        pygame.draw.line(self.boardSprite.image, white, (size - 1, 0), (size - 1, size - 1))
        self.grid = [] # the state of the cells that make up the board. 2d array with each element containing -, r, g, y, or b
        # Initialize the grid. It will be 1 bigger in each dimension than needed to make checking easier (can go off edge)
        row = []
        for i in range(21):
            row.append("-")
        for i in range(21):
            self.grid.append(copy(row))
    
    def draw(self, screen):
        self.boardGroup.draw(screen)
    
    def rect(self):
        return self.boardSprite.rect
    
    def _occupyCells(self, cells, color):
        for cell in cells:
            self.grid[cell[0]][cell[1]] = color[0]
    
    def attemptToOccupy(self, cells, color):
        """ Does rule checking (true iff diagonal connection to same color piece). Raises an InvalidPositionError if the attempt
        fails.
        Cells is a list of x, y tuples. Function assumes cells are all connected. x, y are counted from the top left like 
        the screen coordinates.
        """
        # TODO have cells be a list of BoardPositions which enforce cell constraints and have virtual x and y variables 
        # which can be read from and written to
        for cell in cells:
            assert cell[0] >= 0 and cell[0] < 20
            assert cell[1] >= 0 and cell[1] < 20
        assert str(color) in ["red", "yellow", "green", "blue"]
        
        # All cells must be empty
        for cell in cells:
            if self.grid[cell[0]][cell[1]] != '-':
                raise InvalidPositionError
        # Allow starting positions
        if color == "red":
            startingPosition = (0, 0)
        if color == "yellow":
            startingPosition = (19, 0)
        if color == "green":
            startingPosition = (19, 19)
        if color == "blue":
            startingPosition = (0, 19)
        for cell in cells:
            if cell == startingPosition:
                self._occupyCells(cells, color)
                return
        # No good if same color is touching on a flat side
        for cell in cells:
            if self.grid[cell[0]][cell[1] + 1] == color[0]: raise InvalidPositionError
            if self.grid[cell[0]][cell[1] - 1] == color[0]: raise InvalidPositionError
            if self.grid[cell[0] + 1][cell[1]] == color[0]: raise InvalidPositionError
            if self.grid[cell[0] - 1][cell[1]] == color[0]: raise InvalidPositionError
        # Ok if same color is touching diagonally
        for cell in cells:
            if self.grid[cell[0] + 1][cell[1] + 1] == color[0] or \
                        self.grid[cell[0] + 1][cell[1] - 1] == color[0] or \
                        self.grid[cell[0] - 1][cell[1] + 1] == color[0] or \
                        self.grid[cell[0] - 1][cell[1] - 1] == color[0]: 
                self._occupyCells(cells, color)
                return
        raise InvalidPositionError


if __name__ == "__main__":
    mainWindow = InterBlock()
    mainWindow.mainLoop()
